home *** CD-ROM | disk | FTP | other *** search
- --------------------------------------------------------------------------
- !Labyrinth
- --------------------------------------------------------------------------
-
- Version: 2.17, (05.01.95)
- RiscOS: mindestens V2.00
- RAM: 160 Kbytes
- Autor: Stefan Tomlik
-
- !Labyrinth ist PUBLIC DOMAIN und darf frei kopiert, verbreitet, verändert
- und benutzt werden. Es ist nicht erlaubt, außer zum Zweck der Aufwands-
- entschädigung oder der Begleichung von Versandskosten irgendeinen
- Geldbetrag zu berechnen.
-
-
- --------------------------------------------------------------------------
- Benutzung
- --------------------------------------------------------------------------
-
- Nach dem Start von !Labyrinth werden Sie aufgefordert, die Größe des
- Labyrinth-Feldes festzulegen. Dieser Wert muß innerhalb der vom Programm
- genannten Grenzen liegen. Es wird dann ein quadratisches Feld generiert,
- dessen Kantenlänge der gewünschten Größe entspricht. (Zur Struktur des
- generierten Feldes siehe 'Datenstruktur des Labyrinths'). Direkt nach der
- Generierung werden Start- und Endpunkt im Feld zufällig festgelegt und ein
- Weg vom Start- zum Endpunkt gesucht. (siehe 'Aufbau der graphischen
- Demonstration') Wenn ein möglicher Weg gefunden wurde, läßt Sie das der
- Rechner durch einen Piepton wissen. Danach fragt das Programm mittels
- OS_Confirm, ob ein weiteres Labyrinth generiert werden soll (Antwort
- per Maus: SELECT = Ja, MENU oder ADJUST = Nein).
-
- --------------------------------------------------------------------------
- Beschreibung
- --------------------------------------------------------------------------
-
- !Labyrinth ist eine graphische Demonstration der Arbeitsweise eines
- Backtracking-Algorithmus zur Lösung von beliebigen Labyrinthen. Es wurde mit
- Acorn's ISO Pascal Compiler (V2.2.2) geschrieben. Zur Kompilation werden
- einige Routinen aus der ISO Pascal Extension Library 'xLib' Release 2.00
- benötigt (© 1990, 1991 Nick Smith). Aus rechtlichen Gründen ist es mir nicht
- möglich, diese Library beizulegen, die Routinen dürften allerdings
- problemlos ersetzbar sein:
-
- IMPORT Procedure Bell;
- Ausgabe eines ASCII Standard 'Beep'-Sounds
-
- IMPORT Procedure Hourglass_On;
- IMPORT Procedure Hourglass_Off;
- IMPORT Procedure Hourglass_Percentage (percen: Integer);
- Dazu muß man wohl nicht viel schreiben.
-
- IMPORT Procedure Cursor_Off;
- Equivalent zu *SWI_OS_RemoveCursors
-
- IMPORT Procedure Cursor_On;
- Equivalent zu *SWI_OS_RestoreCursors
-
- IMPORT Function OS_Confirm: Char;
- mögliche Ergebnisse: 'y' für SELECT oder 'n' für irgend eine
- andere Eingabe. Tastatureingaben werden auch interpretiert.
-
- IMPORT Procedure Gcol (plotMode, col: Integer);
- Equivalent zu BASIC's GCOL-Befehl
-
- IMPORT Procedure Plot (command, xpos, ypos: Integer);
- Equivalent zu 'PLOT' in BASIC und dem 'OS_Plot'-SWI. Übergeben
- werden ein Plot-Code und die dazugehörigen Koordinatenparameter.
-
- IMPORT Procedure Move (x, y: Integer);
- (x, y sind absolute Koordinaten (relativ zum Koordinatenursprung)
-
- IMPORT Procedure Draw (x, y: Integer);
- (x, y sind relative Koordinaten zur Position des Graphic-Cursors)
-
- IMPORT Procedure Line (x1, y1, x2, y2: Integer);
- (x1, y1, x2, y2 sind absolute Koordinaten. Benötigt 'Move' )
-
- IMPORT Function Rnd (VAR seed: integer): Real;
- Diese Funktion ergibt die nächste Zahl aus einer einheitlich ver-
- teilten pseudozufälligen Sequenz im Bereich 0.0 <= Rnd < 1.0.
- Die 'x-Lib'-Implementation benötigt dazu eine globale Konstante
- 'startSeed' = 49631 und eine globale Variable 'seed', die zu
- Anfang des Programms (in diesem Program, mittels der standardisierten
- 'Randomize'-Prozedur) auf den Wert von 'startSeed' initialisiert
- werden muß. Diese Variable darf dann nicht mehr (außer durch 'Rnd')
- geändert werden.
-
- IMPORT Function Random (low, high: Integer; VAR seed: integer): Integer;
- Benötigt 'Rnd'. Das Funktionsergebnis ist ein Integerwert im Bereich
- [low..high] (einschließlich).
-
-
- --------------------------------------------------------------------------
- Datenstruktur des Labyrinths (tLab)
- --------------------------------------------------------------------------
-
- Const
- up = 1; right = 2; down = 4; left = 8;
- Type
- tLab = Array[0..maxSize,0..maxSize] Of Record
- w: Byte; { beschreibt die 'Wände' eines Elementes }
- v: Boolean; { (v)isited-Flag; benötigt von 'Possible' }
- End; { tLab }
-
- Der '.w'-Eintrag beinhaltet Informationen darüber, ob an dieser
- Stelle des Labyrinths eine 'Wand' ist oder nicht. Dazu werden die
- Konstanten 'up' bis 'left' benötigt.
-
- lab[x,y].w := RandomInt(5);
-
- Function RandomInt (max: Integer): Integer; { RandomInt = [0..max-1] }
-
- Der '.v'-Eintrag des beschriebenen Datenverbundes wird vom
- eigentlichen Lösungsalgorithmus 'Possible' benötigt, um festzuhalten,
- welche Positionen des Labyrinths' schon 'besucht' (visited) wurden.
- Dies war notwendig, um zu verhindern, daß der Algorithmus im
- ungünstigsten Falle in einer Endlosschleife hängen bleibt.
- - Zu diesem Problem siehe BUGS & FEATURES ....
-
- --------------------------------------------------------------------------
- Prioritätenregelung des Suchvorgangs (tPrioTab)
- --------------------------------------------------------------------------
-
- Type
- tPrioTab = Array[1..4] Of Record
- go: Byte; { eine/mehrere der Richtungen 'up' - 'left' (s.o) }
- ox, oy: Integer; { die dazu benötigen Offsets (s.u.) }
- End; { tPrioTab }
-
- Der Lösungsalgorithmus arbeitet Backtracking-orientiert. Eine
- Implementation anhand der 'Rechte-Hand-Regel' wäre zwar einfacher zu
- verwirklichen gewesen, aber bei weitem nicht so effizient. Es bestünde
- vor allem die Gefahr, daß ein solcher Algorithmus in einer Endlosschleife
- hängen bleibt, wenn davon ausgegangen wird, daß es in dem Labyrinth keine
- Elemente gibt, die nicht mit dem Rest des Labyrinthes in Verbindung stehen,
- und genau das kann von der derzeitigen Initialisierung des Labyrinths nicht
- geleistet werden. Abgesehen davon ist der derzeitige Algorithmus nicht für
- diese Art von Labyrinthen geeignet (siehe unten...). Ich bin während der
- eigentlichen Codierung leider etwas von meinen Vorstellungen abgewichen
- und habe eher ein Programm geschrieben, das in der Lage ist, komplexe
- Hindernisse bestmöglich umgehen zu können.
-
- Das Ergebnis einer Backtracking-Funktion hängt von den Ergebnissen der/des
- rekursiven Aufruf(e) ihrer selbst ab. Das bedeutet außerdem, daß eine klar
- definierte Abbruchbedingung vorhanden sein muß: in 'Possible' wird die Suche
- abgebrochen, wenn der Benutzer den Suchprozeß manuell (mittels Escape)
- unterbricht, der Suchalgorithmus keinen Weg zum Zielpunkt finden konnte oder
- der Zielpunkt gefunden wurde. Es wird nur nach einer Lösung gesucht, und
- zwar der mit dem kürzesten Weg. Um die Suche nach einem Weg zum Zielpunkt
- (definiert durch die Parameter fx, fy) so kurz wie möglich zu halten, wurde
- die Reihenfolge der Suchvorgänge ausgehend von einem Punkt im Labyrinth
- dahingehend optimiert, daß abhängig von der Position (ax, ay) im Verhältnis
- zur Position des Zielpunkts (fx, fy) versucht wird, die Distanz zwischen den
- beiden Punkten so effektiv wie möglich zu verringern.
-
- --------------------------------------------------------------------------
- Aufbau der graphischen Demonstration
- --------------------------------------------------------------------------
-
- Es werden maxSize * maxSize rechteckige Felder dargestellt, die im
- besten Falle durch ihre 'Wände' voneinander unterscheidbar sind. Das
- muß allerdings nicht immer so sein. Der Startpunkt für den
- Lösungsalgorithmus ist mit einem grünen, der Zielpunkt mit einem roten
- Rechteck markiert. Folgendes ist zu beachten: zwei nebeneinander
- liegende Felder können sich überlappende Wände haben (deswegen auch
- der Aufwand in 'Possible'). Dieser Fall ist auf dem Bildschirm nicht
- erkennbar und auch nicht allzu wichtig, da er von 'Possible'
- abgefangen wird, aber man sollte daran denken. Ich hatte es zuerst
- nicht getan und mich gewundert, wieso der Algorithmus trotz seiner
- scheinbaren Korrektheit 'durch die Wand geht'. Die Suche nach einem
- Weg zum Zielpunkt wird grafisch dargestellt. 'Besuchte' Strecken (oder
- Positionen) werden farbig (dunkel-blau) markiert. Zum Thema Farben:
- BUGS & FEATURES. Alle Strecken, die zu keinem Erfolg geführt
- haben, werden nachträglich hell-blau gezeichnet. Die Darstellung ist
- vermutlich nicht nachzuvollziehen - Ich habe Sie gewarnt...
-
- --------------------------------------------------------------------------
- BUGS AND FEATURES ( <-: )
- --------------------------------------------------------------------------
-
- - Die Farbwahl bei der Darstellung wurde mittels 'Gcol' implementiert,
- funktioniert also in 256-Farb-Modi nicht korrekt. Wer sich mit 'Tint'
- auskennt, kann ja mal ergänzen ...
- Die Standard-Farbpalette eines 16-farbigen Modus wird vorausgesetzt.
-
- - Die Prozedure 'Possible' braucht enorm viel Stack, da auf jeder
- rekursiven Ausführungsebene eine lokale Tabelle verwaltet werden muß.
- Leider scheint der Compiler nicht in der Lage zu sein, Code zu
- erzeugen, der Stacks über einen gewissem Limit korrekt verwalten kann
- oder Stack-Overflow zuverlässig abfängt!
- Dieser Fehler kann beobachtet werden, wenn man ein Labyrinth der Größe
- 100*100 wählt und darauf wartet, daß der Lösungsalgorithmus das ganze
- Feld 'abgrasen' muß, um alle Möglichkeiten ausprobiert zu haben. Der
- Rechner wird vermutlich vollständig 'stehenbleiben'. Das scheint ein
- Bug des Compilers zu sein, da der Fehler unabhängig von der
- konfigurierten Größe des System-Heaps/Stacks auftritt. Dieser Absturz
- (selbst das 'Hourglass'-Modul hängt sich auf) kann jederzeit
- reproduziert werden, da die Applikation zur Erzeugung der 'tLab'-
- Felder immer die gleiche Folge von Zufallszahlen benutzt.
- Eine Lösung des Problems wäre, die für die Richtungen benötigten
- Offsets aus der Tabelle herauszunehmen und im Backtracking-Algorithmus
- für jedes Tabellen-Element einzeln zu berechnen. (=> unübersichtlich)
- Es wäre auch möglich, die Prioritätentabelle durch diverse If-Then-
- Konstruktionen, wie geschehen in Init_Priority zu ersetzen, aber der
- Aufwand wäre im Endeffekt genauso groß oder noch größer.
- Soviel also zum Thema AcornSoft. 'The choice of experience in software'!
-
- - Das '.v' (visited) Flag eines Labyrinth-Elementes bestimmt, ob der
- Algorithmus eine bestimmte Strecke 'betreten' darf. Die derzeitige Regel
- besagt, daß eine Position im Labyrinth dann Tabu ist, wenn im einem
- vorherigen Rekursionsdurchlauf die Position schon mal als 'besucht'
- markiert wurde, auch wenn es aus einer anderen Richtung her passierte.
- Das bedeutet, daß eine hinterlassene und noch nicht zurückgenommene
- 'Bahn' eines fehlgeschlagenen Lösungsversuches (der das gesamte Labyrinth
- im schlimmsten Fall genau in der Mitte teilt) nicht überkreuzt werden darf
- und ein Weg um einen Endpunkt dieser 'Bahn' herum gesucht werden muß,
- was dann wieder sehr stack-intensiv ist. Eine Möglichkeit, dieses Problem
- zu lösen: in '.v' nicht festhalten, ob die Position überhaupt schon
- besucht wurde, sondern auch, *VON WOHER* oder *WOHIN DANN*. Dies ließe
- sich durch Variabeln realisieren, die Information darüber enthalten,
- aus welcher Richtung das Feld besucht wurde und/oder welches der nächste
- Rekursionsschritt von dieser Position aus ist. Die derzeitige Regelung
- bedingt, daß bei ungünstigen Voraussetzungen nicht der kürzeste Weg durch
- ein Labyrinth gesucht wird, sondern zwangsläufig der längste! Abgesehen
- davon kann die bisherige 'visited'-Abbruchbedingung zu fehlerhaften
- Ergebnissen führen, da es z.B. nicht erlaubt ist, aus einer ein-spurigen
- Sackgasse umzukehren - der einzige Weg aus der Sackgasse wurde schon
- besucht und der als einziger zum Erfolg führende Rekursionsschritt darf
- nicht ausgeführt werden.
- Nach 'links oder rechts zu gehen' bedeutet übrigens nicht, daß sich der
- hypothetische Labyrinth-Läufer (also im Prinzip der Inhalt der Parameter
- eines Rekursionsdurchlaufs) aus einer Richtung um 90° nach links oder
- rechts dreht und dann einen Schritt nach vorne macht. Ganz im Gegenteil
- kennt der Algorithmus nur die vier absoluten Richtungen, in die er sich
- mittels relativer Koordinaten, Offsets bewegen kann. 'Possible' betrachtet
- (ok, ich weiß...) das Labyrinth-Array aus einer Vogel-Perspektive.
-
- - Keine Unterstützung des WIMP-Environments. Ist da irgend jemand, der
- etwas Zeit dafür hat? Viel Spaß!
-
-
- Name: Stefan Tomlik
- Adr.: Im Dachsbau 11
- Ort: 59199 Bönen
- Tel.: 02383 / 5482
-
- internet: tomlik00@marvin.informatik.uni-dortmund.de
-